Skip to content

fix: route converter-atomic JSON-object nodes through TryModify in TryPatch#85

Merged
IvanMurzak merged 3 commits into
mainfrom
worktree-84-trypatch-objectref
Jun 2, 2026
Merged

fix: route converter-atomic JSON-object nodes through TryModify in TryPatch#85
IvanMurzak merged 3 commits into
mainfrom
worktree-84-trypatch-objectref

Conversation

@IvanMurzak

Copy link
Copy Markdown
Owner

Summary

  • Reflector.TryPatch descended structurally into every JSON-object patch node, so a value whose JSON encoding is an object but which a registered converter resolves indivisibly (e.g. a Unity object reference {"instanceID":"…"}) failed with Segment 'instanceID' not found on type '<T>' instead of being delegated to its converter.
  • Adds a default-false capability flag bool TreatJsonObjectAsAtomicValue(Type) to IReflectionConverter (virtual false in BaseReflectionConverter).
  • In TryPatchInternal, after the leaf checks and before the $type / key-enumeration block, a JSON-object node whose target type has a converter reporting the flag true and that carries no $type key is routed through TryModify — the same converter path componentDiff/pathPatches use. Plain POCO descent and $type polymorphic replacement are unchanged.

The Unity-side one-line override of the flag is a separate downstream task and is not part of this PR.

Test plan

  • New ReflectorNet tests (TryPatchAtomicConverterTests, Unity-agnostic stub converter) cover: converter-atomic object-node routing (member + root), unchanged POCO structural descent, $type replacement, non-object leaf — 5/5 green on net8.0 + net9.0.
  • Suite 1 — ReflectorNet xUnit: 1450/1450 (net8.0 + net9.0).
  • Suite 2 — MCP-Plugin-dotnet consumer against locally-packed ReflectorNet: McpPlugin.Tests 268/268, McpPlugin.Server.Tests 409/409 (net8.0 + net9.0).
  • Suite 3 — Unity-MCP-Plugin EditMode: 953/953.

Closes #84

…yPatch

Reflector.TryPatch structurally descended into every JSON-object patch node,
enumerating its keys as members to navigate. A value whose JSON encoding is an
object but which a registered converter resolves indivisibly (e.g. a Unity
object reference {"instanceID":"…"}) was therefore navigated key-by-key and
failed with "Segment 'instanceID' not found on type '<T>'" instead of being
delegated to the converter that knows how to resolve it.

Add a default-false capability flag bool TreatJsonObjectAsAtomicValue(Type) to
IReflectionConverter (virtual false in BaseReflectionConverter). In
TryPatchInternal, after the leaf checks and before the $type / key-enumeration
block, a JSON-object node whose target type has a converter reporting the flag
true and that carries no "$type" key is routed through TryModify — the same
converter path componentDiff and pathPatches use. Plain POCO sub-objects (flag
false) still descend structurally; $type polymorphic replacement still flows
through the existing structural path; non-object leaves are unchanged.

The Unity-side one-line override of the flag is tracked separately (downstream
task) and is not part of this change.

Closes #84

verified-downstream: McpPlugin.Tests, McpPlugin.Server.Tests, Unity-EditMode
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Test Results

    2 files  ± 0      2 suites  ±0   9m 2s ⏱️ +29s
1 452 tests + 7  1 452 ✅ + 7  0 💤 ±0  0 ❌ ±0 
2 904 runs  +14  2 904 ✅ +14  0 💤 ±0  0 ❌ ±0 

Results for commit be6c959. ± Comparison against base commit 441ec89.

♻️ This comment has been updated with latest results.

…olution

Add a negative test: when a converter-atomic JSON-object node is reached but
its SetValue resolution fails (unresolvable {"ref":"missing-id"}), TryPatch
must return false and surface the converter's own error — it must NOT fall
through to structural key descent, which would re-produce the original #84
failure ("Segment 'ref' not found on type 'AssetRef'"). Asserts the failure
return, the converter error in logs, and the absence of a "not found"
structural-descent message.

verified-downstream: none (test-only; library DLL unchanged)
…s (positional)

Make the converter-atomic JSON-object delegation positional: it fires ONLY for
non-root nodes (depth > 0), i.e. a value being assigned into a member/element
slot. The ROOT node (depth 0) is always patched STRUCTURALLY — the target
object is modified in place by descending into its members.

This removes the need for any type-based scoping in the delegation: a multi-field
patch of an object (including a Unity component) at the root still applies
field-by-field, while a reference encoded as {"instanceID":…} assigned INTO a
member slot is resolved atomically by its converter. Downstream (Unity) the
capability override can therefore be a clean blanket true with no Component /
GameObject exclusion, and a component-reference field by {instanceID} now
resolves as well.

Tests: root-is-structural contract (a'), positional crux proving the same
atomic type is structural at root and atomic as a member (a''), plus the
existing member-depth atomic + failed-resolution cases. All TryPatch_* /
AtomicModifyTests remain green on net8.0 + net9.0.

verified-downstream: pending Unity verification (separate verifier pass)
@IvanMurzak IvanMurzak merged commit c295b89 into main Jun 2, 2026
2 checks passed
@IvanMurzak IvanMurzak deleted the worktree-84-trypatch-objectref branch June 2, 2026 09:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TryPatch descends into JSON-object leaf nodes instead of delegating to the registered converter (object-refs unresolvable via merge-patch)

1 participant